/*
 * Copyright (C) 2009-2015 FBReader.ORG Limited <contact@fbreader.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package com.koolearn.kooreader.book;

import com.koolearn.klibrary.core.filesystem.ZLArchiveEntryFile;
import com.koolearn.klibrary.core.filesystem.ZLFile;
import com.koolearn.klibrary.core.filesystem.ZLPhysicalFile;
import com.kooreader.util.Pair;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;

public final class FileInfoSet {
    private final HashMap<ZLFile, FileInfo> myInfosByFile = new HashMap<ZLFile, FileInfo>();
    private final HashMap<FileInfo, ZLFile> myFilesByInfo = new HashMap<FileInfo, ZLFile>();
    private final HashMap<Pair<String, FileInfo>, FileInfo> myInfosByPair =
            new HashMap<Pair<String, FileInfo>, FileInfo>();
    private final HashMap<Long, FileInfo> myInfosById = new HashMap<Long, FileInfo>();

    private final LinkedHashSet<FileInfo> myInfosToSave = new LinkedHashSet<FileInfo>();
    private final LinkedHashSet<FileInfo> myInfosToRemove = new LinkedHashSet<FileInfo>();

    private final BooksDatabase myDatabase;

    public FileInfoSet(BooksDatabase database) {
        myDatabase = database;
        load(database.loadFileInfos());
    }

    public FileInfoSet(BooksDatabase database, ZLFile file) {
        myDatabase = database;
        load(database.loadFileInfos(file));
    }

    FileInfoSet(BooksDatabase database, long fileId) {
        myDatabase = database;
        load(database.loadFileInfos(fileId)); // 进入一个while循环,一直查询file_id对应的parent_id，一直到parent_id为null。
        // 跳出后继续执行下面的方法
    }

    private void load(Collection<FileInfo> infos) {
        for (FileInfo info : infos) {
            // 更新两个属性,后面的方法会调用
            myInfosByPair.put(new Pair(info.Name, info.Parent), info);
            myInfosById.put(info.Id, info);
            // 对象创建好后回到createBook BookDataBase
        }
    }

    public void save() {
        myDatabase.executeAsTransaction(new Runnable() {
            public void run() {
                for (FileInfo info : myInfosToRemove) {
                    myDatabase.removeFileInfo(info.Id);
                    myInfosByPair.remove(new Pair(info.Name, info.Parent));
                }
                myInfosToRemove.clear();
                for (FileInfo info : myInfosToSave) {
                    myDatabase.saveFileInfo(info);
                }
                myInfosToSave.clear();
            }
        });
    }

    public boolean check(ZLPhysicalFile file, boolean processChildren) {
        if (file == null) {
            return true;
        }
        final long fileSize = file.size();
        FileInfo info = get(file);
        if (info.FileSize == fileSize) {
            return true;
        } else {
            info.FileSize = fileSize;
            if (processChildren && !"epub".equals(file.getExtension())) {
                removeChildren(info);
                myInfosToSave.add(info);
                addChildren(file);
            } else {
                myInfosToSave.add(info);
            }
            return false;
        }
    }

    public List<ZLFile> archiveEntries(ZLFile file) {
        final FileInfo info = get(file);
        if (!info.hasChildren()) {
            return Collections.emptyList();
        }
        final LinkedList<ZLFile> entries = new LinkedList<ZLFile>();
        for (FileInfo child : info.subtrees()) {
            if (!myInfosToRemove.contains(child)) {
                entries.add(ZLArchiveEntryFile.createArchiveEntryFile(file, child.Name));
            }
        }
        return entries;
    }

    private FileInfo get(String name, FileInfo parent) {
        final Pair pair = new Pair(name, parent);
        FileInfo info = myInfosByPair.get(pair);
        if (info == null) {
            info = new FileInfo(name, parent);
            myInfosByPair.put(pair, info);
            myInfosToSave.add(info);
        }
        return info;
    }


    private FileInfo get(ZLFile file) {
        if (file == null) {
            return null;
        }
        FileInfo info = myInfosByFile.get(file);
        if (info == null) {
            info = get(file.getLongName(), get(file.getParent()));
            myInfosByFile.put(file, info);
        }
        return info;
    }

    public long getId(ZLFile file) {
        final FileInfo info = get(file);
        if (info == null) {
            return -1;
        }
        if (info.Id == -1) {
            save();
        }
        return info.Id;
    }

    private ZLFile getFile(FileInfo info) { // 调用createFile()
        if (info == null) { // 没有parent时为null
            return null;
        }
        ZLFile file = myFilesByInfo.get(info);
        if (file == null) {
            // 进入递归调用
            file = ZLFile.createFile(getFile(info.Parent), info.Name);
            myFilesByInfo.put(info, file);
        }
        return file;
    }


    public ZLFile getFile(long id) {
        return getFile(myInfosById.get(id)); // 继续调用getFile
    }

    private void removeChildren(FileInfo info) {
        for (FileInfo child : info.subtrees()) {
            if (myInfosToSave.contains(child)) {
                myInfosToSave.remove(child);
            } else {
                myInfosToRemove.add(child);
            }
            removeChildren(child);
        }
    }

    private void addChildren(ZLFile file) {
        for (ZLFile child : file.children()) {
            final FileInfo info = get(child);
            if (myInfosToRemove.contains(info)) {
                myInfosToRemove.remove(info);
            } else {
                myInfosToSave.add(info);
            }
            addChildren(child);
        }
    }
}
